package org.fglxy.mockserver.mock;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.script.ScriptException;

import org.fglxy.mockserver.entity.MockEndEntity;
import org.fglxy.mockserver.entity.MockMessage;
import org.fglxy.mockserver.entity.MockTaskEntity;
import org.fglxy.mockserver.enums.RetCodeEnum;
import org.fglxy.mockserver.exception.MessageException;
import org.fglxy.mockserver.netty.NettyServer;
import org.fglxy.mockserver.netty.WebSocketServer;
import org.fglxy.mockserver.util.JSInvoker;
import org.fglxy.mockserver.util.JSONUtils;
import org.fglxy.mockserver.util.ProtoUtils;
import org.fglxy.mockserver.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.protobuf.Message;
import com.googlecode.protobuf.format.JsonFormat;
import com.googlecode.protobuf.format.JsonFormat.ParseException;

import io.netty.channel.Channel;

/**
 * mock服务端
 * 
 * @author xiaoyangliu
 *
 */
public class MockServer implements IMockEnd {
	private static Logger LOGGER = LoggerFactory.getLogger(MockServer.class);

	// 当前mock客户端的触发任务表
	private CopyOnWriteArrayList<JSInvoker> taskList = new CopyOnWriteArrayList<>();

	private MockEndEntity server;

	private NettyServer nettyServer;

	private ConcurrentHashMap<String, ProxyClient> proxyMap;

	private ConcurrentHashMap<String, Channel> channelMap;
	
	private Map<String, Object> context = new HashMap<>();

	public MockServer(MockEndEntity server) {
		this.server = server;
		this.proxyMap = new ConcurrentHashMap<String, ProxyClient>();
		this.channelMap = new ConcurrentHashMap<String, Channel>();
		this.context.put("ip", server.getServerIp());
		this.context.put("port", server.getServerPort());
	}

	public void init() {
		try {
			doInit();
		} catch (Exception e) {
			throw new MessageException(RetCodeEnum.START_MOCK_FAIL, e);
		}
	}

	private void doInit() throws IOException, ScriptException {
		this.taskList.clear();
		ResourceUtils.readLock(this.getMockId());
		try {
			ProtoUtils.createDescriptor(this.getMockId());
		} catch(Exception e) {
			LOGGER.error("error:", e);
		} finally {
			ResourceUtils.releaseReadLock(this.getMockId());
		}
		
		List<MockTaskEntity> tasks = ResourceUtils.getTasksByMockId(this.server.getId());
		if (tasks != null) {
			LOGGER.info("task count:{}", tasks.size());
			for (MockTaskEntity task : tasks) {
				taskList.add(JSInvoker.getInstance(task.getScript()));
			}
		}
		else {
			LOGGER.info("task null");
		}
	}

	public void start() {
		try {
			this.nettyServer = new NettyServer(this.server.getServerPort());
			this.nettyServer.start(this);
			LOGGER.info("mockserver start");
			WebSocketServer.Instance.sendMsg(this.getMockId(), "mockserver启动");
		} catch (InterruptedException e) {
			LOGGER.warn("mock client interrupted:", e);
		}
	}

	public void stop() {
		this.nettyServer.getChannel().close();
		this.nettyServer.release();
		for (Entry<String, ProxyClient> kv : this.proxyMap.entrySet()) {
			kv.getValue().stop();
		}
		this.proxyMap.clear();
	}

	public void restart() {
		this.stop();
		this.init();
		this.start();
	}

	@Override
	public void onReceive(Channel channel, MockMessage msg) {
		WebSocketServer.Instance.sendMsg(this.getMockId(), "接收到消息:cmdno:" + msg.getCmdNo());
		boolean hasMock = false;
		// 将protobuf消息转为map结构，方便传参给js脚本
		@SuppressWarnings("unchecked")
		Map<String, Object> json = msg.getMsg() != null ? 
				JSONUtils.parse(JsonFormat.printToString(msg.getMsg()), Map.class)
				: null;
		// 遍历所有任务，检查是否有对应触发事件，若有则执行
		for (JSInvoker task : taskList) {
			try {
				// 重置脚本执行上下文
				task.reset();
				String functionName = "onReceive_" + msg.getCmdNo();
				// 判断是否存在回调
				if (!task.hasFunction(functionName)) {
					continue;
				}
				hasMock = true;
				// 调用js脚本并发送mock消息
				callFunctionAndSendMessage(channel, task, functionName, json);
			} catch (Exception e) {
				LOGGER.error("exception:", e);
			}
		}
		// mock没处理就转发真实服务器
		if (!hasMock) {
			List<MockMessage> msgs = null;
			// 遍历所有任务，检查是否有对应触发事件，若有则执行
			for (JSInvoker task : taskList) {
				try {
					// 重置脚本执行上下文
					task.reset();
					String functionName = "onProxy_" + msg.getCmdNo();
					// 判断是否存在回调
					if (!task.hasFunction(functionName)) {
						continue;
					}
					msgs = getMessages(task, functionName, json);
					//查看是否需要篡改消息（有时候消息体中包含ip信息，服务端会校验。如果使用中间层的话就无法通过了）
					if (msgs != null) {
						break;
					}
				} catch (Exception e) {
					LOGGER.error("exception:", e);
				}
			}
			if (msgs != null && msgs.size() > 0) {
				LOGGER.info("修改后的消息为:{}", msgs.get(0));
				this.proxyMap.get(channel.id().asLongText()).sendMsg(msgs.get(0));
			}
			else {
				this.proxyMap.get(channel.id().asLongText()).sendMsg(msg);
			}
		}
	}

	public void sendMsg(String ip, int port, MockMessage msg) {
		this.channelMap.get(ip + "_" + port).writeAndFlush(msg);
	}

	/*
	 * 当连接建立时的回调消息。这里会扫描所有任务，如果存在触发条件为连接建立的消息则向对端发送
	 * 
	 * @see org.fglxy.mockserver.mock.IMockEnd#onConnect()
	 */
	@Override
	public void onConnect(Channel channel) {
		WebSocketServer.Instance.sendMsg(this.getMockId(), "与服务端连接建立完成");
		// 遍历所有任务，检查是否有对应触发事件，若有则执行
		for (JSInvoker task : taskList) {
			try {
				task.reset();
				String functionName = "onConnect";
				if (!task.hasFunction(functionName)) {
					continue;
				}
				callFunctionAndSendMessage(channel, task, functionName);
			} catch (Exception e) {
				LOGGER.error("exception:", e);
			}
		}
		// 创建代理服务
		ProxyClient proxy = new ProxyClient(this.getMockId(), this.server.getRealServerIp(),
				this.server.getRealServerPort(), channel);
		proxy.init();
		proxy.start();
		this.proxyMap.put(channel.id().asLongText(), proxy);
		InetSocketAddress addr = (InetSocketAddress) channel.remoteAddress();
		this.channelMap.put(addr.getAddress().getHostAddress() + "_" + addr.getPort(), channel);
	}

	private void callFunctionAndSendMessage(Channel channel, JSInvoker task, String functionName)
			throws NoSuchMethodException {
		callFunctionAndSendMessage(channel, task, functionName, null);
	}
	
	private List<MockMessage> getMessages(JSInvoker task, String functionName,
			@SuppressWarnings("rawtypes") Map params) throws NoSuchMethodException {
		final List<MockMessage> msgList = new ArrayList<>();
		// 调用脚本方法，返回必须为数组形式，数组中每项为一个请求协议。如果只需要做一个请求，则数组中只有一项即可
		task.callFunction(functionName, params, this.context).forEach((str, obj) -> {
			@SuppressWarnings("unchecked")
			Map<String, Object> script = (Map<String, Object>) obj;
			// 拼接报文
			int cmdNo = Integer.valueOf(script.get("cmdNo").toString());
			// 这里的协议必须指定
			Message.Builder builder = ProtoUtils.getBuilderByCmdNo(this.getMockId(), cmdNo);
			String datas = JSONUtils.toJson(script.get("datas"));
			try {
				JsonFormat.merge(datas, builder);
			} catch (ParseException e) {
				WebSocketServer.Instance.sendMsg(this.getMockId(), "格式化对端消息时出现异常:" + e.getMessage());
				LOGGER.error("format message error:", e);
			}
			Message proto = builder.build();
			
			msgList.add(new MockMessage(cmdNo, proto));
		});
		return msgList;
	}

	private void callFunctionAndSendMessage(Channel channel, JSInvoker task, String functionName,
			@SuppressWarnings("rawtypes") Map params) throws NoSuchMethodException {
		List<MockMessage> msgs = getMessages(task, functionName, params);
		if (msgs == null) {
			return;
		}
		for (MockMessage msg : msgs) {
			// 发送对端消息
			WebSocketServer.Instance.sendMsg(this.getMockId(), "向对端发送消息:cmdNo:" + msg.getCmdNo() + ";proto:" + msg.getMsg());
			channel.writeAndFlush(msg);
		}
	}

	/**
	 * 客户端连接断开
	 */
	@Override
	public void onConnectFinish(Channel channel) {
		InetSocketAddress addr = (InetSocketAddress) channel.remoteAddress();
		this.channelMap.remove(addr.getAddress().getHostAddress() + "_" + addr.getPort());
		this.proxyMap.get(channel.id().asLongText()).stop();
		this.proxyMap.remove(channel.id().asLongText());
		LOGGER.info("与客户端连接断开");
	}

	@Override
	public String getMockId() {
		return this.server.getId();
	}

}
